//
//  NMTextNode.swift
//  NetEaseMusic
//
//  Created by SAGESSE on 2020/2/29.
//  Copyright © 2020 SAGESSE. All rights reserved.
//

import UIKit
import AsyncDisplayKit


class NMTextNode: ASTextNode {
    
    var attachments: [NMTextNodeAttachment]?
    
    override var attributedText: NSAttributedString? {
        get { return super.attributedText }
        set {
            // Updating attributedText is expensive, so don't update it without changing.
            guard newValue != super.attributedText else {
                return
            }
            
            // Clean the old attachment node.
            self.attachments?.forEach {
                $0.display.removeFromSupernode()
            }
            self.attachments = nil
            
            // There is no need to process when newvalue is null.
            guard let newValue = newValue?.mutableCopy() as? NSMutableAttributedString else {
                return
            }
            
            // Char offsets and new attachments.
            var offset = 0
            var attachments = [NMTextNodeAttachment]()

            // Use detector to examine the entire string.
            Self.detector?.enumerateMatches(in: newValue.string, range: NSMakeRange(0, newValue.length)) { result, _, _ in
                // Match successful, check the match result。
                guard var range = result?.range, range.location != NSNotFound else {
                    return
                }
                
                // Calibration the matches range.
                range.location += offset
                
                // Gets the inserted point attributes.
                let attributes = newValue.attributes(at: range.location, effectiveRange: nil)
                let descender = (attributes[.font] as? UIFont)?.descender ?? 0
                
                // If the contents is a link needs to replace the specific content.
                let contents = newValue.mutableString.substring(with: range)
                switch contents.first {
                case "[" where contents.count > 2: // This is a emoticon.
                    // Make a new emotion text.
                    let emoji = String(contents[contents.index(after: contents.startIndex) ..< contents.index(before: contents.endIndex)])
                    
                    // This is a named emoji?
                    if let target = NMEmoji.shared.emoji(for: emoji) as NSString? {
                        newValue.replaceCharacters(in: range, with: target as String)
                        offset -= range.length - target.length
                        range = NSMakeRange(range.location, target.length)
                        newValue.addAttributes(attributes, range: range)
                        return
                    }
                    
                    // This is a custom emotion(sync display)
                    if let target = NMEmoji.shared.image(for: emoji) {
                        let attachment = NSTextAttachment()
                        attachment.image = target
                        attachment.bounds = .init(x: 0, y: descender, width: target.size.width, height: target.size.height)
                        newValue.replaceCharacters(in: range, with: NSAttributedString(attachment: attachment))
                        offset -= range.length - 1
                        range = NSMakeRange(range.location, 1)
                        newValue.addAttributes(attributes, range: range)
                        return
                    }
                    
                    // This is a custom emotion(async display)
                    if let target = NMEmoji.shared.url(for: emoji) {
                        let attachment = NMTextNodeAttachment(target)
                        attachment.bounds = .init(x: 0, y: descender - 2.5, width: 22, height: 22)
                        newValue.replaceCharacters(in: range, with: NSAttributedString(attachment: attachment))
                        offset -= range.length - 1
                        range = NSMakeRange(range.location, 1)
                        newValue.addAttributes(attributes, range: range)
                        attachments.append(attachment)
                        return
                    }
                    
                    // This is a unknow emoji, ignore.
                    return
                    
                case "@": // This is a at.
                    break
                    
                case "#": // This is a topic.
                    break
                    
                default: // This is a normal url.
                    // Make a new link text.
                    let target = NSMutableAttributedString(string: "网页连接").then {
                        let attachment = NSTextAttachment2()
                        attachment.image = #imageLiteral(resourceName: "cm2_act_icn_link")
                        attachment.margin = .init(top: 0, left: 2, bottom: 0, right: 2)
                        attachment.alignment = .bottom
                        $0.insert(.init(attachment: attachment), at: 0)
                    }
                    newValue.replaceCharacters(in: range, with: target)
                    offset -= range.length - target.length
                    range = NSMakeRange(range.location, target.length)
                    newValue.addAttributes(attributes, range: range)
                }
                
                // Update the highlighted attributes.
                newValue.addAttribute(Self.link, value: contents, range: range)
                newValue.addAttribute(.foregroundColor, value: NMTheme.shared.color(forKey: "event.link"), range: range)
            }
            
            // Reattach new attachment node.
            attachments.forEach {
                addSubnode($0.display)
            }
            
            self.attachments = attachments
            super.attributedText = newValue
        }
    }
    
    
    override init() {
        super.init()
        self.linkAttributeNames.append(Self.link.rawValue)
    }
    
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        
        guard bounds.contains(point), isUserInteractionEnabled, !isHidden, alpha > 0.01 else {
            return false
        }
        
        return linkAttributeValue(at: point, attributeName: nil, range: nil) != nil
    }
    
    /// Custom link for textkit.
    static var link: NSAttributedString.Key = .init(rawValue: "NMTextNode.link")

    /// Create a at & link & topic detector regular expression
    static var detector: NSRegularExpression? = try? .init(pattern: "(@[\\u4e00-\\u9fa5a-zA-Z0-9_-]+|\\[[^\\[\\]]+\\]|#[^\\s\\r\\n\\[\\]/#]+#|\\w{3,}://[\\w.:;?=\\-%+#/]+)")
}


// MARK: -


/// The text attachment for text node.
class NMTextNodeAttachment: NSTextAttachment {
    
    /// A async displayed image url.
    var url: URL?
    
    /// A async display node for attachment.
    var display: ASNetworkImageNode = .init()

    /// A remote image url.
    convenience init(_ url: URL) {
        self.init()
        self.url = url
        self.display.isHidden = true
        self.display.isLayerBacked = true
        self.display.contentMode = .scaleAspectFill
        self.display.style.preferredSize = .init(width: 38, height: 38)
    }
    
    /// When the current attachment is displayed, will requeste a image.
    override func image(forBounds imageBounds: CGRect, textContainer: NSTextContainer?, characterIndex charIndex: Int) -> UIImage? {
        
        display.isHidden = false
        display.url = url

        display.layoutThatFits(ASSizeRangeMake(.zero, imageBounds.size))
        display.frame = imageBounds.offsetBy(dx: 0, dy: -imageBounds.height)

        return nil
    }
}
